package scenic;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;

import scenic.geom.TransformBuilder;
import scenic.jni.Renderer;
import scenic.path.AWTShapePath;
import scenic.path.Path;
import scenic.path.PathWalker;

public class CommandBuffer {
	private static final boolean printDebugInfo = false;

	private Object objects[] = new Object[10000];
	private int objectsTail;
	private int objectsHead;
	private int objectsSize;

	private double doubles[] = new double[10000];
	private int doublesTail;
	private int doublesHead;
	private int doublesSize;
	
	private Environment env;
	private Thread renderingThread;
	
	public CommandBuffer() {
		env = new Environment();
		renderingThread = new Thread(env, "Scenic rendering thread");
		System.out.println("start");
		renderingThread.start();
		System.out.println("started");
	}
	
	@SuppressWarnings("serial")
	private static class RectanglePath extends Rectangle implements Path {
		public void walk(PathWalker walker, AffineTransform errorMatrix,
				double error) {
			walker.beginSubPath(true);
			walker.lineTo(x, y);
			walker.lineTo(x + width, y);
			walker.lineTo(x + width, y + height);
			walker.lineTo(x, y + height);
			walker.endSubPath();
		}
		
		public Rectangle getBounds(AffineTransform transform) {
			return Util.transform(transform, this);
		}
		
		public boolean isConvex() {
			return true;
		}
	}
	
	private static class Cache {
		private static AWTShapePath shapePath = new AWTShapePath(null);
		private static RectanglePath rectanglePath = new RectanglePath();
		private static StrokedPath strokedPath = new StrokedPath(shapePath);
		private static FilledPath filledPath = new FilledPath(shapePath);
		private static SceneImage sceneImage = new SceneImage(null);
		private static TextShape textShape = new TextShape(null, null, null);
		
		static {
			strokedPath.setColor(new ScenicColor());
			filledPath.setColor(new ScenicColor());
			textShape.setColor(new ScenicColor());
		}
	}
	
	public class Context implements SceneParent {
		private Context parent;
		private int depth = 0;
		private ScenicImage image;
		private ScenicJFrame frame;
		
		private Paint paint;
		private Stroke stroke;
		private Composite composite;
		private Font font;
		private AffineTransform transform = new AffineTransform();
		
		public Context(Context c) {
			parent = c;
			depth = parent.depth + 1;
			setDefaults();
			image = c.image;
			frame = c.frame;
			create();
		}
		
		public Context(ScenicImage image) {
			this.image = image;
			setDefaults();
			create();
		}
		
		public Context(ScenicJFrame frame) {
			this.frame = frame;
			setDefaults();
			create();
		}
		
		private void set(Context c) {
			paint = c.paint;
			stroke = c.stroke;
			composite = c.composite;
			font = c.font;
			transform.setTransform(c.transform);
		}
		
		private void create() {
			synchronized(CommandBuffer.this) {
				beginCommand(CreateContextCmd.instance);
				writeObject(parent);
				endCommand();
			}
		}
		
		private void setDefaults() {
			paint = Color.BLACK;
			stroke = new BasicStroke();
			font = new Font("sanserif", Font.PLAIN, 12);
		}
		
		private void beginCommand(Command command) {
			writeObject(command);
			writeObject(this);
		}
		
		private void endCommand() {
		}
		
		public void dispose() {
		}
		
		private DrawContext createDrawContext() {
			DrawContext context = new DrawContext();
			
			context.root = this;
			context.renderingInstance = new Object();
			context.renderer = new Renderer();

			if(image != null) {
				context.width = image.getWidth();
				context.height = image.getHeight();
				context.context = context.renderer.createContextForImage(image.getId());
			} else {
				context.width = frame.getWidth();
				context.height = frame.getHeight();
				context.context = context.renderer.createContextForCanvas(frame.getId());
			}
			
			context.AAFilter = SceneSettings.getDefaultAAFilter();
			
			return context;
		}
		
		public void setPaint(Paint p) {
			synchronized(CommandBuffer.this) {
				beginCommand(SetPaint.instance);
				writeObject(p);
				endCommand();
			}
		}
		
		public void setStroke(Stroke stroke) {
			synchronized(CommandBuffer.this) {
				beginCommand(SetStrokeCmd.instance);
				writeObject(stroke);
				endCommand();
			}
		}
		
		public void setComposite(Composite composite) {
			synchronized(CommandBuffer.this) {
				beginCommand(SetCompositeCmd.instance);
				writeObject(composite);
				endCommand();
			}
		}
		
		public void setFont(Font font) {
			synchronized(CommandBuffer.this) {
				beginCommand(SetFontCmd.instance);
				writeObject(font);
				endCommand();
			}
		}
		
		public void setTransform(AffineTransform t) {
			synchronized(CommandBuffer.this) {
				beginCommand(SetTransform.instance);
				writeDouble(t.getScaleX());
				writeDouble(t.getShearX());
				writeDouble(t.getShearY());
				writeDouble(t.getScaleY());
				writeDouble(t.getTranslateX());
				writeDouble(t.getTranslateY());
				endCommand();
			}
		}
		
		public void concatenateTransform(AffineTransform t) {
			synchronized(CommandBuffer.this) {
				beginCommand(ConcatenateTransform.instance);
				writeDouble(t.getScaleX());
				writeDouble(t.getShearX());
				writeDouble(t.getShearY());
				writeDouble(t.getScaleY());
				writeDouble(t.getTranslateX());
				writeDouble(t.getTranslateY());
				endCommand();
			}
		}
		
		public void translate(double x, double y) {
			synchronized(CommandBuffer.this) {
				beginCommand(Translate.instance);
				writeDouble(x);
				writeDouble(y);
				endCommand();
			}
		}	
		
		public void clip(Shape shape) {
		}
		
		public void setClip(Shape shape) {
		}
		
		public void draw(Shape shape) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawCmd.instance);
				writeObject(shape);
				endCommand();
			}
		}
		
		public void fill(Shape shape) {
			synchronized(CommandBuffer.this) {
				beginCommand(FillCmd.instance);
				writeObject(shape);
				endCommand();
			}
		}
		
		public void drawRect(int x, int y, int width, int height) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawRectangleCmd.instance);
				writeDouble(x);
				writeDouble(y);
				writeDouble(width);
				writeDouble(height);
				endCommand();
			}
		}
		
		public void fillRect(int x, int y, int width, int height) {
			synchronized(CommandBuffer.this) {
				beginCommand(FillRectangleCmd.instance);
				writeDouble(x);
				writeDouble(y);
				writeDouble(width);
				writeDouble(height);
				endCommand();
			}
		}
		
		public void drawImage(ScenicImage img, double x, double y) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawImageCmd.instance);
				writeObject(img);
				writeDouble(x);
				writeDouble(y);
				endCommand();
			}
		}
		
		public void drawImage(ScenicImage img,
                int dx1, int dy1, int dx2, int dy2,
                int sx1, int sy1, int sx2, int sy2) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawScaledImageCmd.instance);
				writeObject(img);
				writeDouble(dx1);
				writeDouble(dy1);
				writeDouble(dx2);
				writeDouble(dy2);
				writeDouble(sx1);
				writeDouble(sy1);
				writeDouble(sx2);
				writeDouble(sy2);
				endCommand();
			}
		}
		
		public void drawString(String str, double x, double y) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawStringCmd.instance);
				writeObject(str);
				writeDouble(x);
				writeDouble(y);
				endCommand();
			}
		}

		public void drawGlyphVector(GlyphVector gv, double x, double y) {
			synchronized(CommandBuffer.this) {
				beginCommand(DrawGlyphVectorCmd.instance);
				writeObject(gv.getFont());
				writeObject(gv.getGlyphCodes(0, gv.getNumGlyphs(), null));
				writeObject(gv.getGlyphPositions(0, gv.getNumGlyphs(), null));
				writeDouble(x);
				writeDouble(y);
				endCommand();
			}
		}

		public void childChanged(Rectangle area) {
		}
	}
	
	public class Environment implements Runnable {
		public Context context;
		public DrawContext drawContext;
		public AffineTransform parentTransform = new AffineTransform();
		public Rectangle visibleArea;
		
		public void renderBuffer() {
			HashMap<Command, Integer> usageCount = new HashMap<Command, Integer>();
			
			drawContext = null;
			context = null;
			
			new Renderer().enterCriticalSection();
			
			for(;;) {
				Command cmd = (Command)readObject();
				Context ctx = (Context)readObject();
				
//				System.out.println("Command: " + cmd);
				if(ctx == null) {
					cmd.execute(this, ctx);
					continue;
				}
				if(context == null || context.image != ctx.image || 
						context.frame != ctx.frame) {
//					System.out.println("New context: " + newCtx);
					endRendering();
					context = ctx;
					beginRendering();
				} else {
					context = ctx;					
				}
//				System.out.println("Cmd: " + cmd + "Ctx: " + ctx);
				try {
					cmd.execute(this, ctx);
				} catch(Exception e) {
					e.printStackTrace();
				}	
				
				Integer count = usageCount.get(cmd);					
				if(count == null) {
					count = new Integer(0);
				}
				count = new Integer(count.intValue() + 1);
				usageCount.put(cmd, count);
			}
			
/*			System.out.println("Usage counts");
			for(Command cmd : usageCount.keySet()) {
				System.out.println(cmd + " " + usageCount.get(cmd));
			}
			System.out.println(); */
		}
		
		public void beginRendering() {
			if(context != null && drawContext == null) {
				drawContext = context.createDrawContext();
//				System.out.println("new context: " + drawContext.context);
				visibleArea = new Rectangle(0, 0, drawContext.width, drawContext.height);
				if(!drawContext.renderer.beginScene(drawContext.context)) {
					System.out.println("beginScene failed");
				}
			}
		}
		
		public void endRendering() {
//			System.out.println("end rendering");
			if(drawContext != null) {
				drawContext.renderer.endScene(drawContext.context);
				drawContext.renderer.present(drawContext.context);
				drawContext.renderer.deleteContext(drawContext.context);
				drawContext = null;
			}
		}
			
		public void readTransform(AffineTransform t) {
			double m00 = readDouble();
			double m01 = readDouble();
			double m10 = readDouble();
			double m11 = readDouble();
			double m02 = readDouble();
			double m12 = readDouble();
			
			t.setTransform(m00, m10, m01, m11, m02, m12);
		}	
		
		public Object readObject() {
			return CommandBuffer.this.readObject();
		}
		
		public double readDouble() {
			return CommandBuffer.this.readDouble();
		}

		public void run() {
			renderBuffer();
		}
	}
	
	public synchronized void addCommand(CustomCommand cmd) {
		if(Thread.currentThread() == Renderer.getRenderingThread()) {
			cmd.execute(null, null);
		} else {
			writeObject(cmd);
			writeObject(null);
		}
	}
	
	private void writeObject(Object obj) {
		while(objectsSize == objects.length) {
//			System.out.println("Wait write");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		objects[objectsHead] = obj;
		objectsHead++;
		if(objectsHead >= objects.length)
			objectsHead = 0;
		objectsSize++;
		notifyAll();
	}
	
	private void writeDouble(double value) {
		while(doublesSize == doubles.length) {
//			System.out.println("Wait write");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		doubles[doublesHead] = value;
		doublesHead++;
		if(doublesHead >= doubles.length)
			doublesHead = 0;
		doublesSize++;
		notifyAll();
	}
	
	long endRenderingTime;
	
	private void waitRead() {
//		System.out.println("Wait read");
		env.endRendering();
		new Renderer().leaveCriticalSection();
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}			
		new Renderer().enterCriticalSection();
		env.beginRendering();
	}
	
	public synchronized Object readObject() {
		while(objectsSize == 0) {
			waitRead();
		}
		if(printDebugInfo) {
			System.out.println();
			System.out.println(objects[objectsTail]);
		}
		notifyAll();
		Object obj = objects[objectsTail];
		objects[objectsTail] = null;
		objectsTail++;
		objectsSize--;
		if(objectsTail >= objects.length)
			objectsTail = 0;
		return obj;
	}
	
	public synchronized double readDouble() {
		while(doublesSize == 0) {
			waitRead();
		}
		if(printDebugInfo) {
			System.out.println();
			System.out.println(doubles[doublesTail]);
		}
		notifyAll();
		double value = doubles[doublesTail++];
		
		if(doublesTail >= doubles.length)
			doublesTail = 0;
		doublesSize--;
		return value;
	}

	
	public static abstract class Command {
		public int numDoubles;
		public int numObjects;
		
		protected static AffineTransform t = new AffineTransform();
		
		abstract void execute(Environment env, Context ctx);
		
		public void draw(Environment env, Context ctx, SceneNode node, AffineTransform tm,
				Rectangle bounds) {
			if(ctx.composite == null) {
				node.draw(env.drawContext, tm, bounds);
			} else if(ctx.composite instanceof AlphaComposite) {
				AlphaComposite ac = (AlphaComposite)ctx.composite;
				float alpha = ac.getAlpha();
				ScenicColor color = new ScenicColor(alpha, alpha, alpha, alpha);
				SceneClip clip = new SceneClip(new SolidColor(color));
				
				clip.add(node);
				
				clip.draw(env.drawContext, tm, bounds);
			}
		}
		
		public void paint(Environment env, Context ctx, SceneShape shape,
				AffineTransform transform) {
			if(ctx.paint instanceof Color) {
				Color color = (Color)ctx.paint;
				ScenicColor c = shape.getColor();
				
				c.red = color.getRed() / 255.0f;
				c.green = color.getGreen() / 255.0f;
				c.blue = color.getBlue() / 255.0f;
				c.alpha = color.getAlpha() / 255.0f;
				
				draw(env, ctx, shape, transform, env.visibleArea);
			} else if(ctx.paint instanceof GradientPaint) {
				GradientPaint gp = (GradientPaint)ctx.paint;
				LinearColorFunction f = new LinearColorFunction();
				Gradient gradient;
				SceneClip clip;
				AffineTransform tm;
				
				f.addNode(0, new ScenicColor(gp.getColor1()));
				f.addNode(1, new ScenicColor(gp.getColor2()));
				
				gradient = new Gradient();
				gradient.setFunction(f);
				gradient.setType(Gradient.LINEAR);
				gradient.setCycleMethod(gp.isCyclic() ? Gradient.REFLECT : Gradient.NO_CYCLE);
				
				double dx = gp.getPoint2().getX() - gp.getPoint1().getX();
				double dy = gp.getPoint2().getY() - gp.getPoint1().getY();
				
				tm = new AffineTransform(dx, dy, -dy, dx, gp.getPoint1().getX(), gp.getPoint1().getY());
				gradient.setTransform(tm);
				
				clip = new SceneClip(shape);
				clip.add(gradient);
				draw(env, ctx, clip, transform, env.visibleArea);
			} else if(ctx.paint instanceof TexturePaint) {
				TexturePaint tp = (TexturePaint)ctx.paint;
				ScenicImage img = ScenicImage.convert(tp.getImage());
				Rectangle2D ar = tp.getAnchorRect();
				Pattern pattern = new Pattern(img.getWidth(), img.getHeight());
				SceneClip clip;
				SceneTransform st;
				
				pattern.setPattern(new SceneImage(img));
				clip = new SceneClip(shape);
				clip.add(pattern);
				st = new SceneTransform(new TransformBuilder().translateBy(ar.getX(), ar.getY()).
						scaleBy(ar.getWidth() / img.getWidth(), ar.getHeight() / img.getHeight()));
				st.add(clip);
				draw(env, ctx, st, transform, env.visibleArea);
			}
		}
	}
	
	public static abstract class CustomCommand extends Command {
		public boolean executed = false;
		
		synchronized void execute(Environment env, Context ctx) {	
			execute();
			executed = true;
			notifyAll();
		}
		
		public synchronized void waitCompletion() {
			while(!executed) {
				try {
					wait();
				} catch (InterruptedException e) {
				}
			}
		}
		
		public abstract void execute();
	}
	
	public static class CreateContextCmd extends Command {
		public static Command instance = new CreateContextCmd();
		
		public CreateContextCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			Context parent = (Context)env.readObject();
			
			if(parent == null) {
				ctx.setDefaults();
				ctx.transform = (AffineTransform)env.parentTransform.clone();
			} else {
				ctx.set(parent);
			}
		}
	}
	
	public static class SetPaint extends Command {	
		public static Command instance = new SetPaint();

		public SetPaint() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			ctx.paint = (Paint)env.readObject();
		}
	}

	public static class SetCompositeCmd extends Command {	
		public static Command instance = new SetCompositeCmd();

		public SetCompositeCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			ctx.composite = (Composite)env.readObject();
		}
	}

	public static class SetFontCmd extends Command {	
		public static Command instance = new SetFontCmd();

		public SetFontCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			ctx.font = (Font)env.readObject();
		}
	}
	
	public static class SetTransform extends Command {	
		public static Command instance = new SetTransform();

		private static AffineTransform t = new AffineTransform();
		
		public SetTransform() {
			numDoubles = 6;
		}
		
		void execute(Environment env, Context ctx) {
			env.readTransform(t);
			ctx.transform.setTransform(env.parentTransform);
			ctx.transform.concatenate(t);
		}
	}
	
	public static class ConcatenateTransform extends Command {	
		public static Command instance = new ConcatenateTransform();

		private static AffineTransform t = new AffineTransform();
		
		public ConcatenateTransform() {
			numDoubles = 6;
		}
		
		void execute(Environment env, Context ctx) {
			env.readTransform(t);
			ctx.transform.concatenate(t);
		}
	}
	
	public static class Translate extends Command {	
		public static Command instance = new Translate();

		public Translate() {
			numDoubles = 2;
		}
		
		void execute(Environment env, Context ctx) {
			double x = env.readDouble();
			double y = env.readDouble();
			
			ctx.transform.translate(x, y);
		}
	}
	
	public static class SetStrokeCmd extends Command {	
		public static Command instance = new SetStrokeCmd();

		public SetStrokeCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			ctx.stroke = (Stroke)env.readObject();
		}
	}
	
	public static class ClipCmd extends Command {	
		public static Command instance = new ClipCmd();

		public ClipCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
/*			Shape shape = (Shape)env.readObject();
			
			if(shape instanceof Rectangle) {
				Rectangle r = (Rectangle)shape;
				
				env.context.renderer.beginClip(env.context.context, )
			}
			Cache.shapePath.shape = shape;
			draw(env, ctx, Cache.shapePath); */
		}
	}
	
	public static class DrawCmd extends Command {	
		public static Command instance = new DrawCmd();

		public DrawCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			Shape shape = (Shape)env.readObject();
			
			Cache.shapePath.shape = shape;
			draw(env, ctx, Cache.shapePath);
		}
		
		void draw(Environment env, Context ctx, Path path) {
			if(!(ctx.paint instanceof Color))
				return;
			StrokedPath strokedPath = Cache.strokedPath;
			BasicStroke stroke = (BasicStroke)ctx.stroke;
			Color color = (Color)ctx.paint;
			ScenicColor c = strokedPath.getColor();
			
			c.red = color.getRed() / 255.0f;
			c.green = color.getGreen() / 255.0f;
			c.blue = color.getBlue() / 255.0f;
			c.alpha = color.getAlpha() / 255.0f;
			
			strokedPath.setPath(path);
			
			strokedPath.setLineWidth(stroke.getLineWidth());
			strokedPath.setEndCap(stroke.getEndCap());
			strokedPath.setLineJoin(stroke.getLineJoin());
			strokedPath.setMiterLimit(stroke.getMiterLimit());
			
			float dashArray[] = stroke.getDashArray(); 
			
			if(dashArray != null) {
				float newDashArray[] = new float[dashArray.length % 2 == 0 ? 
						dashArray.length : 2 * dashArray.length];
			
//				System.out.println("dash " + dashArray.length);
				for(int i = 0; i < newDashArray.length; i++) {
					newDashArray[i] = dashArray[i % dashArray.length] / stroke.getLineWidth();
//					System.out.println(newDashArray[i]);
				}
				dashArray = newDashArray;
			}
			
			strokedPath.setDashArray(dashArray);
			strokedPath.setDashPhase(stroke.getDashPhase() * stroke.getLineWidth());
			
			paint(env, ctx, strokedPath, ctx.transform);		
		}
	}
	
	public static class DrawRectangleCmd extends DrawCmd {
		public static Command instance = new DrawRectangleCmd();

		public DrawRectangleCmd() {
			numDoubles = 4;
		}
		
		void execute(Environment env, Context ctx) {
			Rectangle r = Cache.rectanglePath;
			
			r.x = (int)env.readDouble();
			r.y = (int)env.readDouble();
			r.width = (int)env.readDouble();
			r.height = (int)env.readDouble();
			
			draw(env, ctx, Cache.rectanglePath);
		}
	}
	
	public static class FillCmd extends Command {	
		public static Command instance = new FillCmd();

		public FillCmd() {
			numObjects = 1;
		}
		
		void execute(Environment env, Context ctx) {
			Shape shape = (Shape)env.readObject();
			
			Cache.shapePath.shape = shape;
			fill(env, ctx, Cache.shapePath);
		}
		
		void fill(Environment env, Context ctx, Path path) {
			FilledPath filledPath = Cache.filledPath;
			
			filledPath.setPath(path);
			
			paint(env, ctx, filledPath, ctx.transform);
//			filledPath.draw(env.context, ctx.transform, env.visibleArea);			
		}
	}
	
	public static class FillRectangleCmd extends FillCmd {
		public static Command instance = new FillRectangleCmd();

		public FillRectangleCmd() {
			numDoubles = 4;
		}
		
		void execute(Environment env, Context ctx) {
			Rectangle r = Cache.rectanglePath;
			
			r.x = (int)env.readDouble();
			r.y = (int)env.readDouble();
			r.width = (int)env.readDouble();
			r.height = (int)env.readDouble();
			
			fill(env, ctx, Cache.rectanglePath);
		}
		
	}
	
	public static class DrawImageCmd extends Command {	
		public static Command instance = new DrawImageCmd();

		public DrawImageCmd() {
			numObjects = 1;
			numDoubles = 2;
		}
		
		void execute(Environment env, Context ctx) {
			ScenicImage img = (ScenicImage)env.readObject();
			double x = env.readDouble();
			double y = env.readDouble();
			
			Cache.sceneImage.setImage(img);
			Cache.sceneImage.setSourceRect(null);

			t.setTransform(ctx.transform);
			t.translate(x, y);
			draw(env, ctx, Cache.sceneImage, t, env.visibleArea);
		}
	}
	
	public static class DrawScaledImageCmd extends Command {	
		public static Command instance = new DrawScaledImageCmd();

		public DrawScaledImageCmd() {
			numObjects = 1;
			numDoubles = 8;
		}
		
		void execute(Environment env, Context ctx) {
			ScenicImage img = (ScenicImage)env.readObject();
			double dx1, dy1, dx2, dy2;
			double sx1, sy1, sx2, sy2;
			
			dx1 = env.readDouble();
			dy1 = env.readDouble();
			dx2 = env.readDouble();
			dy2 = env.readDouble();
			
			sx1 = env.readDouble();
			sy1 = env.readDouble();
			sx2 = env.readDouble();
			sy2 = env.readDouble();
			
			Cache.sceneImage.setImage(img);
			Cache.sceneImage.setSourceRect(new Rectangle2D.Double(sx1, sy1, sx2 - sx1, sy2 - sy1));

			t.setTransform(ctx.transform);
			t.translate(dx1, dy1);
			t.scale((dx2 - dx1) / (sx2 - sx1), (dy2 - dy1) / (sy2 - sy1));
			draw(env, ctx, Cache.sceneImage, t, env.visibleArea);
		}
	}
	
	public static class DrawStringCmd extends Command {	
		public static Command instance = new DrawStringCmd();
		
		private static AffineTransform t = new AffineTransform();
		
		public DrawStringCmd() {
			numObjects = 1;
			numDoubles = 2;
		}
				
		void execute(Environment env, Context ctx) {
			String str = (String)env.readObject();
			double x = env.readDouble();
			double y = env.readDouble();
			
			if(!(ctx.paint instanceof Color))
				return;
			Color color = (Color)ctx.paint;
			ScenicColor c = Cache.textShape.getColor();
			FontRenderContext frc = new FontRenderContext(
						ctx.transform,
						true, false); //  state.usesFractionalFontMetrics);
			
			c.red = color.getRed() / 255.0f;
			c.green = color.getGreen() / 255.0f;
			c.blue = color.getBlue() / 255.0f;
			c.alpha = color.getAlpha() / 255.0f;
						
			GlyphVector gv = ctx.font.createGlyphVector(frc, str);

			int[] glyphCodes;
			float[] positions;
			
			positions = gv.getGlyphPositions(0, gv.getNumGlyphs(), null);
			glyphCodes = gv.getGlyphCodes(0, gv.getNumGlyphs(), null);
			
			Cache.textShape.setFont(ctx.font);
			Cache.textShape.setGlyphs(glyphCodes, positions);

			t.setTransform(ctx.transform);
			t.translate(x, y);
			draw(env, ctx, Cache.textShape, t, env.visibleArea);
		}
	}
	
	public static class DrawGlyphVectorCmd extends Command {	
		public static Command instance = new DrawGlyphVectorCmd();
		
		private static AffineTransform t = new AffineTransform();		
		
		public DrawGlyphVectorCmd() {
			numObjects = 3;
			numDoubles = 2;
		}
				
		void execute(Environment env, Context ctx) {
			Font font = (Font)env.readObject();
			int[] glyphCodes = (int[])env.readObject();
			float[] positions = (float[])env.readObject();
			double x = env.readDouble();
			double y = env.readDouble();
			
			if(!(ctx.paint instanceof Color))
				return;
			Color color = (Color)ctx.paint;
			ScenicColor c = Cache.textShape.getColor();
			
			c.red = color.getRed() / 255.0f;
			c.green = color.getGreen() / 255.0f;
			c.blue = color.getBlue() / 255.0f;
			c.alpha = color.getAlpha() / 255.0f;
								
			Cache.textShape.setFont(font);
			Cache.textShape.setGlyphs(glyphCodes, positions);

			t.setTransform(ctx.transform);
			t.translate(x, y);
			draw(env, ctx, Cache.textShape, t, env.visibleArea);
		}
	}

	public Context createContext(Context parent) {
		return new Context(parent);
	}
	
	public Context createContext(ScenicJFrame frame) {
		return new Context(frame);
	}

	public Context createContext(ScenicImage image) {
		return new Context(image);
	}
	
	private static final CommandBuffer instance = new CommandBuffer();
	
	public static CommandBuffer getInstance() {
		return instance;
	}
}
